#ifndef BHXJQSHADER_H
#define BHXJQSHADER_H

#include <glm/glm.hpp>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include "bhxjglinclude.h"
#include <QFile>
#include "BHXJControls_global.h"
class BHXJCONTROLS_EXPORT BHXJGLShader
{
public:
    unsigned int ID=0;
    BHXJGLShader()
    {

    }
    ~BHXJGLShader()
    {
        if(this->ID!=0&&this->gl!=nullptr)
        {
            this->gl->glDeleteProgram(this->ID);
        }
    }


    void Init(BHXJGLFUN *glcore, const QString& vertexPath, const QString &fragmentPath, const QString &geometryPath = nullptr)
    {
        this->gl=glcore;


        unsigned int vertex;
        unsigned int fragment;
        unsigned int geometry=0;
        //顶点着色器
        {
            QFile vShaderFile(vertexPath);
            vShaderFile.open(QIODevice::ReadOnly);
            QByteArray bytearray=vShaderFile.readAll();
            const char *ShaderCode=bytearray.data();

            vertex =  gl->glCreateShader(GL_VERTEX_SHADER);

            gl->glShaderSource(vertex, 1, &ShaderCode, nullptr);
            gl->glCompileShader(vertex);

            checkCompileErrors(vertex, "VERTEX");
        }
        //片元着色器
        {
            QFile vShaderFile(fragmentPath);
            vShaderFile.open(QIODevice::ReadOnly);
            QByteArray bytearray=vShaderFile.readAll();
            const char *ShaderCode=bytearray.data();

            fragment =  gl->glCreateShader(GL_FRAGMENT_SHADER);

            gl->glShaderSource(fragment, 1, &ShaderCode, nullptr);
            gl->glCompileShader(fragment);

            checkCompileErrors(fragment, "FRAGMENT");
        }
        //几何着色器
        if(geometryPath!=nullptr)
        {
            QFile vShaderFile(geometryPath);
            vShaderFile.open(QIODevice::ReadOnly);
            QByteArray bytearray=vShaderFile.readAll();
            const char *ShaderCode=bytearray.data();

            geometry =  gl->glCreateShader(GL_GEOMETRY_SHADER);

            gl->glShaderSource(geometry, 1, &ShaderCode, nullptr);
            gl->glCompileShader(geometry);

            checkCompileErrors(geometry, "GEOMETRY");
        }

        //着色程序链接
        ID = gl->glCreateProgram();
        gl->glAttachShader(ID, vertex);
        gl->glAttachShader(ID, fragment);
        if(geometryPath != nullptr)
            gl->glAttachShader(ID, geometry);
        gl->glLinkProgram(ID);
        checkCompileErrors(ID, "PROGRAM");
        // delete the shaders as they're linked into our program now and no longer necessery
        gl->glDeleteShader(vertex);
        gl->glDeleteShader(fragment);
        if(geometryPath != nullptr)
            gl->glDeleteShader(geometry);

    }


    //
    // ------------------------------------------------------------------------
    void use()
    {
        gl->glUseProgram(ID);
    }
    //
    void setBool(const std::string &name, bool value) const
    {
        gl->glUniform1i(gl->glGetUniformLocation(ID, name.c_str()), static_cast<int>(value));
    }
    //
    void setInt(const std::string &name, int value) const
    {
        gl->glUniform1i(gl->glGetUniformLocation(ID, name.c_str()), value);
    }
    //
    void setFloat(const std::string &name, float value) const
    {
        gl->glUniform1f(gl->glGetUniformLocation(ID, name.c_str()), value);
    }
    // ------------------------------------------------------------------------
    void setVec2(const std::string &name, const glm::vec2 &value) const
    {
        gl->glUniform2fv(gl->glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
    }
    void setVec2(const std::string &name, float x, float y) const
    {
        gl->glUniform2f(gl->glGetUniformLocation(ID, name.c_str()), x, y);
    }
    // ------------------------------------------------------------------------
    void setVec3(const std::string &name, const glm::vec3 &value) const
    {
        gl->glUniform3fv(gl->glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
    }
    void setVec3(const std::string &name, float x, float y, float z) const
    {
        gl->glUniform3f(gl->glGetUniformLocation(ID, name.c_str()), x, y, z);
    }
    // ------------------------------------------------------------------------
    void setVec4(const std::string &name, const glm::vec4 &value) const
    {
        gl->glUniform4fv(gl->glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
    }
    void setVec4(const std::string &name, float x, float y, float z, float w)
    {
        gl->glUniform4f(gl->glGetUniformLocation(ID, name.c_str()), x, y, z, w);
    }
    // ------------------------------------------------------------------------
    void setMat2(const std::string &name, const glm::mat2 &mat) const
    {
        gl->glUniformMatrix2fv(gl->glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }
    // ------------------------------------------------------------------------
    void setMat3(const std::string &name, const glm::mat3 &mat) const
    {
        gl-> glUniformMatrix3fv(gl->glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }
    // ------------------------------------------------------------------------
    void setMat4(const std::string &name, const glm::mat4 &mat) const
    {
        gl-> glUniformMatrix4fv(gl->glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }
    void setMat4(const int &location, const glm::mat4 &mat) const
    {
        gl-> glUniformMatrix4fv(location, 1, GL_FALSE, &mat[0][0]);
    }

    int getUniformLocation(const std::string &name) const
    {
        return gl->glGetUniformLocation(ID, name.c_str());
    }

private:
    BHXJGLFUN *gl;
    // utility function for checking shader compilation/linking errors.
    // ------------------------------------------------------------------------
    void checkCompileErrors(GLuint shader, std::string type)
    {
        GLint success;
        GLchar infoLog[1024];
        if(type != "PROGRAM")
        {
            gl->glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
            if(!success)
            {
                gl->glGetShaderInfoLog(shader, 1024, nullptr, infoLog);
                std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
        else
        {
            gl->glGetProgramiv(shader, GL_LINK_STATUS, &success);
            if(!success)
            {
                gl->glGetProgramInfoLog(shader, 1024, nullptr, infoLog);
                std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
    }
};
#endif
